package middleware

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/golang-jwt/jwt/v4"
	"github.com/zeromicro/go-zero/core/logx"
	"github.com/zeromicro/go-zero/core/stores/redis"
	"github.com/zeromicro/go-zero/rest/httpc"
	"github.com/zeromicro/go-zero/rest/httpx"
	"github.com/zeromicro/go-zero/rest/token"
	"gitlab.zhijiasoft.com/paperless-group/saas-common/config"
	"gitlab.zhijiasoft.com/paperless-group/saas-common/core/auth"
	"gitlab.zhijiasoft.com/paperless-group/saas-common/errorx"
	"io"
	"net/http"
)

const (
	jwtAudience    = "aud"
	jwtExpire      = "exp"
	jwtId          = "jti"
	jwtIssueAt     = "iat"
	jwtIssuer      = "iss"
	jwtNotBefore   = "nbf"
	jwtSubject     = "sub"
	noDetailReason = "no detail reason"
)

// Authenticator is the interface that wraps the Authenticate method.
type Authenticator interface {
	Authenticate(r *http.Request, w http.ResponseWriter) (*http.Request, auth.UserObj, error)
	CheckToken(ctx context.Context, token string) (auth.UserObj, error)
}

// AuthMiddleware is the middleware that authenticates the request.
type AuthMiddleware struct {
	Authenticators []Authenticator
}

// NewAuthMiddleware returns a new AuthMiddleware.
func NewAuthMiddleware(authenticators ...Authenticator) *AuthMiddleware {
	return &AuthMiddleware{
		Authenticators: authenticators,
	}
}

func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		r, u, err := m.Authenticate(r, w)
		if err != nil {
			//httpx.Error(w, errorx.NewApiUnauthorizedError("认证失败"))
			httpx.Error(w, errorx.NewApiUnauthorizedError(err.Error()))
			return
		}
		r, err = m.SetContext(r, u)

		next(w, r)
		return

	}
}

func (m *AuthMiddleware) SetContext(r *http.Request, userObj auth.UserObj) (*http.Request, error) {
	// set context
	ctx := r.Context()
	ctx = context.WithValue(ctx, "tenantId", userObj.GetTenantID())
	ctx = context.WithValue(ctx, "userId", userObj.GetID())

	r = r.WithContext(ctx)
	return r, nil
}

func (m *AuthMiddleware) Authenticate(r *http.Request, w http.ResponseWriter) (*http.Request, auth.UserObj, error) {
	for _, a := range m.Authenticators {
		r, user, err := a.Authenticate(r, w)
		if err == nil {
			return r, user, nil
		}
	}
	return r, nil, errorx.NewApiUnauthorizedError("认证失败")
}

// ---------- local authenticator

type LocalUser struct {
	ID       string `json:"id"`
	TenantID string `json:"tenantId"`
}

func (u *LocalUser) GetID() string {
	return u.ID
}

func (u *LocalUser) GetTenantID() string {
	return u.TenantID
}

type LocalAuthenticator struct {
	Rds      *redis.Redis
	AuthConf *config.AuthConf
}

func NewLocalAuthenticator(
	rds *redis.Redis,
	authConf *config.AuthConf) *LocalAuthenticator {

	return &LocalAuthenticator{
		Rds:      rds,
		AuthConf: authConf,
	}
}

func (s *LocalAuthenticator) ParseToken(r *http.Request, secret, prevSecret string) (*jwt.Token, error) {

	// check jwt blacklist
	{
		jwtResult, err := s.Rds.Get("token_" + r.Header.Get("Authorization"))
		if err != nil {
			logx.Errorw("redis error in jwt", logx.Field("detail", err.Error()))
			return nil, errorx.NewApiError(http.StatusInternalServerError, err.Error())
		}
		if jwtResult == "1" {
			logx.Errorw("token in blacklist", logx.Field("detail", r.Header.Get("Authorization")))
			return nil, errorx.NewApiErrorWithoutMsg(http.StatusUnauthorized)
		}
	}

	// parse token
	parser := token.NewTokenParser()
	tok, err := parser.ParseToken(r, secret, prevSecret)
	if err != nil {
		return nil, err
	}

	if !tok.Valid {
		return nil, errorx.NewApiUnauthorizedError("token is invalid")
	}

	return tok, err
}

func (s *LocalAuthenticator) Authenticate(r *http.Request, w http.ResponseWriter) (*http.Request, auth.UserObj, error) {
	// parse token
	tok, err := s.ParseToken(r, s.AuthConf.AccessSecret, s.AuthConf.AccessSecret)

	if err != nil {
		return r, nil, err
	}

	claims, ok := tok.Claims.(jwt.MapClaims)
	if !ok {
		return r, nil, errorx.NewApiUnauthorizedError("token is invalid")
	}

	// set context
	ctx := r.Context()
	for k, v := range claims {
		switch k {
		case jwtAudience, jwtExpire, jwtId, jwtIssueAt, jwtIssuer, jwtNotBefore, jwtSubject:
			// ignore the standard claims
		default:
			ctx = context.WithValue(ctx, k, v)
		}
	}

	r = r.WithContext(ctx)

	// get user id
	//userId, ok:= tok.Claims.(jwt.MapClaims)["userId"].(string)
	userId, ok := r.Context().Value("userId").(string)
	if !ok {
		logx.Errorw("user info error", logx.Field("detail", r.Header.Get("Authorization")))

		return r, nil, errorx.NewApiUnauthorizedError("用户信息解析失败")
	}
	tenantId, ok := r.Context().Value("tenantId").(string)
	if !ok {
		logx.Errorw("user info error", logx.Field("detail", r.Header.Get("Authorization")))

		return r, nil, errorx.NewApiUnauthorizedError("用户信息解析失败")
	}

	return r, &LocalUser{ID: userId, TenantID: tenantId}, nil
}

func (s *LocalAuthenticator) CheckToken(ctx context.Context, token string) (auth.UserObj, error) {
	claims := jwt.MapClaims{}
	_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (any, error) {
		return []byte(s.AuthConf.AccessSecret), nil
	})
	if err != nil {
		return nil, err
	}
	// set context
	for k, v := range claims {
		switch k {
		case jwtAudience, jwtExpire, jwtId, jwtIssueAt, jwtIssuer, jwtNotBefore, jwtSubject:
			// ignore the standard claims
		default:
			ctx = context.WithValue(ctx, k, v)
		}
	}

	// get user id
	userId, ok := ctx.Value("userId").(string)
	if !ok {
		logx.Errorw("user info error", logx.Field("detail", token))

		return nil, errorx.NewApiUnauthorizedError("用户信息解析失败")
	}
	tenantId, ok := ctx.Value("tenantId").(string)
	if !ok {
		logx.Errorw("user info error", logx.Field("detail", token))

		return nil, errorx.NewApiUnauthorizedError("用户信息解析失败")
	}

	return &LocalUser{ID: userId, TenantID: tenantId}, nil
}

// RemoteAuthenticator is a remote authenticator that authenticates user by
type RemoteAuthenticator struct {
	AuthBackend *config.AuthBackend
}

func NewRemoteAuthenticator(authBackend *config.AuthBackend) *RemoteAuthenticator {

	return &RemoteAuthenticator{
		AuthBackend: authBackend,
	}
}

func (s *RemoteAuthenticator) Authenticate(r *http.Request, w http.ResponseWriter) (*http.Request, auth.UserObj, error) {

	jwtResult := r.Header.Get("Authorization")
	if jwtResult == "" {
		logx.Errorw("there is no token in headers", logx.Field("detail", "there is no token in headers"))
		return r, nil, errorx.NewApiUnauthorizedError("token is missing")
	}
	userObj, err := s.CheckToken(r.Context(), jwtResult)
	if err != nil {
		return r, nil, err
	}

	// set context
	ctx := r.Context()
	ctx = context.WithValue(ctx, "tenantId", userObj.GetTenantID())
	ctx = context.WithValue(ctx, "userId", userObj.GetID())

	r = r.WithContext(ctx)

	return r, userObj, nil
}

func (s *RemoteAuthenticator) CheckToken(ctx context.Context, token string) (auth.UserObj, error) {
	type Request struct {
		Token string `json:"token"` // json 请求体，这里以 string 为例，你可以使用任意类型
		AppID string `header:"X-APPID"`
	}

	type Response struct {
		Code int                    `json:"code"`
		Data map[string]interface{} `json:"data"`
	}
	// 拼装参数  执行认证请求
	req := Request{
		Token: token,
		AppID: s.AuthBackend.Client.AppID,
	}

	// get remote user info
	resp, err := httpc.Do(ctx, http.MethodPost, s.AuthBackend.Client.Domain+s.AuthBackend.AuthenticateURL, req)
	if err != nil {
		fmt.Println(err)
		return nil, errorx.NewApiUnauthorizedError("认证失败")
	}

	var respMap Response
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
		logx.Errorw("Authenticate request failed: ", logx.Field("err", err.Error()))
		return nil, errorx.NewApiUnauthorizedError("认证失败")
	}

	err = json.Unmarshal(body, &respMap)
	if err != nil {
		logx.Errorw("Authenticate response parse failed: ", logx.Field("err", err.Error()))
		return nil, errorx.NewApiUnauthorizedError("认证失败")
	} else if respMap.Code != 0 {
		return nil, errorx.NewApiUnauthorizedError("认证失败")
	}

	userId, ok := respMap.Data[s.AuthBackend.UserIDField].(string)
	if !ok && userId != "" {
		fmt.Println(err)
		logx.Errorw("userId parse failed: ", logx.Field("err", err.Error()))
		return nil, errorx.NewApiUnauthorizedError("认证失败")
	}
	tenantId, ok := respMap.Data[s.AuthBackend.TenantIDField].(string)
	if !ok && tenantId != "" {
		fmt.Println(err)
		logx.Errorw("tenantId parse failed: ", logx.Field("err", err.Error()))
		return nil, errorx.NewApiUnauthorizedError("认证失败")
	}
	return &LocalUser{ID: userId, TenantID: tenantId}, nil
}
